#include "duckdb/storage/storage_info.hpp"

#include "duckdb/common/numeric_utils.hpp"
#include "duckdb/common/optional_idx.hpp"

namespace duckdb {
constexpr idx_t Storage::MAX_ROW_GROUP_SIZE;
constexpr idx_t Storage::MAX_BLOCK_ALLOC_SIZE;
constexpr idx_t Storage::MIN_BLOCK_ALLOC_SIZE;
constexpr idx_t Storage::DEFAULT_BLOCK_HEADER_SIZE;

const uint64_t VERSION_NUMBER = 64;
const uint64_t VERSION_NUMBER_LOWER = 64;
const uint64_t VERSION_NUMBER_UPPER = 67;

static_assert(VERSION_NUMBER_LOWER <= VERSION_NUMBER, "Check on VERSION_NUMBER lower bound");
static_assert(VERSION_NUMBER <= VERSION_NUMBER_UPPER, "Check on VERSION_NUMBER upper bound");

struct StorageVersionInfo {
	const char *version_name;
	idx_t storage_version;
};

struct SerializationVersionInfo {
	const char *version_name;
	idx_t serialization_version;
};

// These sections are automatically generated by scripts/generate_storage_info.py
// Do not edit them manually, your changes will be overwritten
// clang-format off
// START OF STORAGE VERSION INFO
const uint64_t DEFAULT_STORAGE_VERSION_INFO = 64;
static const StorageVersionInfo storage_version_info[] = {
	{"v0.0.4", 1},
	{"v0.1.0", 1},
	{"v0.1.1", 1},
	{"v0.1.2", 1},
	{"v0.1.3", 1},
	{"v0.1.4", 1},
	{"v0.1.5", 1},
	{"v0.1.6", 1},
	{"v0.1.7", 1},
	{"v0.1.8", 1},
	{"v0.1.9", 1},
	{"v0.2.0", 1},
	{"v0.2.1", 1},
	{"v0.2.2", 4},
	{"v0.2.3", 6},
	{"v0.2.4", 11},
	{"v0.2.5", 13},
	{"v0.2.6", 15},
	{"v0.2.7", 17},
	{"v0.2.8", 18},
	{"v0.2.9", 21},
	{"v0.3.0", 25},
	{"v0.3.1", 27},
	{"v0.3.2", 31},
	{"v0.3.3", 33},
	{"v0.3.4", 33},
	{"v0.3.5", 33},
	{"v0.4.0", 33},
	{"v0.5.0", 38},
	{"v0.5.1", 38},
	{"v0.6.0", 39},
	{"v0.6.1", 39},
	{"v0.7.0", 43},
	{"v0.7.1", 43},
	{"v0.8.0", 51},
	{"v0.8.1", 51},
	{"v0.9.0", 64},
	{"v0.9.1", 64},
	{"v0.9.2", 64},
	{"v0.10.0", 64},
	{"v0.10.1", 64},
	{"v0.10.2", 64},
	{"v0.10.3", 64},
	{"v1.0.0", 64},
	{"v1.1.0", 64},
	{"v1.1.1", 64},
	{"v1.1.2", 64},
	{"v1.1.3", 64},
	{"v1.2.0", 65},
	{"v1.2.1", 65},
	{"v1.2.2", 65},
	{"v1.3.0", 66},
	{"v1.3.1", 66},
	{"v1.3.2", 66},
	{"v1.4.0", 67},
	{"v1.4.1", 67},
	{"v1.4.2", 67},
	{"v1.4.3", 67},
	{"v1.5.0", 67},
	{nullptr, 0}
};
// END OF STORAGE VERSION INFO
static_assert(DEFAULT_STORAGE_VERSION_INFO == VERSION_NUMBER, "Check on VERSION_INFO");

// START OF SERIALIZATION VERSION INFO
const uint64_t LATEST_SERIALIZATION_VERSION_INFO = 7;
const uint64_t DEFAULT_SERIALIZATION_VERSION_INFO = 1;
static const SerializationVersionInfo serialization_version_info[] = {
	{"v0.10.0", 1},
	{"v0.10.1", 1},
	{"v0.10.2", 1},
	{"v0.10.3", 2},
	{"v1.0.0", 2},
	{"v1.1.0", 3},
	{"v1.1.1", 3},
	{"v1.1.2", 3},
	{"v1.1.3", 3},
	{"v1.2.0", 4},
	{"v1.2.1", 4},
	{"v1.2.2", 4},
	{"v1.3.0", 5},
	{"v1.3.1", 5},
	{"v1.3.2", 5},
	{"v1.4.0", 6},
	{"v1.4.1", 6},
	{"v1.4.2", 6},
	{"v1.4.3", 6},
	{"v1.5.0", 7},
	{"latest", 7},
	{nullptr, 0}
};
// END OF SERIALIZATION VERSION INFO
// clang-format on

static_assert(DEFAULT_SERIALIZATION_VERSION_INFO <= LATEST_SERIALIZATION_VERSION_INFO,
              "Check on SERIALIZATION_VERSION_INFO");

string GetStorageVersionName(const idx_t serialization_version, const bool add_suffix) {
	if (serialization_version < 4) {
		// special handling for lower serialization versions
		return "v1.0.0+";
	}
	optional_idx min_idx;
	for (idx_t i = 0; serialization_version_info[i].version_name; i++) {
		if (strcmp(serialization_version_info[i].version_name, "latest") == 0) {
			continue;
		}
		if (serialization_version_info[i].serialization_version != serialization_version) {
			continue;
		}
		if (!min_idx.IsValid()) {
			min_idx = i;
		}
	}
	if (!min_idx.IsValid()) {
		D_ASSERT(0);
		return "--UNKNOWN--";
	}

	auto min_name = string(serialization_version_info[min_idx.GetIndex()].version_name);
	if (add_suffix) {
		min_name += "+";
	}
	return min_name;
}

optional_idx GetStorageVersion(const char *version_string) {
	for (idx_t i = 0; storage_version_info[i].version_name; i++) {
		if (!strcmp(storage_version_info[i].version_name, version_string)) {
			return storage_version_info[i].storage_version;
		}
	}
	return optional_idx();
}

optional_idx GetSerializationVersion(const char *version_string) {
	for (idx_t i = 0; serialization_version_info[i].version_name; i++) {
		if (!strcmp(serialization_version_info[i].version_name, version_string)) {
			return serialization_version_info[i].serialization_version;
		}
	}
	return optional_idx();
}

vector<string> GetSerializationCandidates() {
	vector<string> candidates;
	for (idx_t i = 0; serialization_version_info[i].version_name; i++) {
		candidates.push_back(serialization_version_info[i].version_name);
	}
	return candidates;
}

string GetDuckDBVersions(idx_t version_number) {
	vector<string> versions;
	for (idx_t i = 0; storage_version_info[i].version_name; i++) {
		if (version_number == storage_version_info[i].storage_version) {
			versions.push_back(string(storage_version_info[i].version_name));
		}
	}
	if (versions.empty()) {
		return string();
	}
	string result;
	for (idx_t i = 0; i < versions.size(); i++) {
		string sep = "";
		if (i) {
			sep = i + 1 == versions.size() ? " or " : ", ";
		}
		result += sep;
		result += versions[i];
	}
	return result;
}

void Storage::VerifyBlockAllocSize(const idx_t block_alloc_size) {
	if (!IsPowerOfTwo(block_alloc_size)) {
		throw InvalidInputException("the block size must be a power of two, got %llu", block_alloc_size);
	}
	if (block_alloc_size < MIN_BLOCK_ALLOC_SIZE) {
		throw InvalidInputException(
		    "the block size must be greater or equal than the minimum block size of %llu, got %llu",
		    MIN_BLOCK_ALLOC_SIZE, block_alloc_size);
	}
	if (block_alloc_size > MAX_BLOCK_ALLOC_SIZE) {
		throw InvalidInputException(
		    "the block size must be lesser or equal than the maximum block size of %llu, got %llu",
		    MAX_BLOCK_ALLOC_SIZE, block_alloc_size);
	}
	auto max_value = NumericCast<idx_t>(NumericLimits<int32_t>().Maximum());
	if (block_alloc_size > max_value) {
		throw InvalidInputException(
		    "the block size must not be greater than the maximum 32-bit signed integer value of %llu, got %llu",
		    max_value, block_alloc_size);
	}
}

void Storage::VerifyBlockHeaderSize(const idx_t block_header_size) {
	if ((block_header_size & 7) != 0) {
		// Alignment to 8 bytes is necessary for computing the checksum
		throw InvalidInputException("the block size must a multiple of 8, got %llu", block_header_size);
	}
	if (block_header_size < DEFAULT_BLOCK_HEADER_SIZE) {
		throw InvalidInputException(
		    "the block header size must be greater or equal than the default block header of %llu, got %llu",
		    DEFAULT_BLOCK_HEADER_SIZE, block_header_size);
	}
	if (block_header_size > MAX_BLOCK_HEADER_SIZE) {
		throw InvalidInputException(
		    "the block header size must be lesser or equal than the maximum block size of %llu, got %llu",
		    MAX_BLOCK_ALLOC_SIZE, block_header_size);
	}
}

} // namespace duckdb
